import express, { Application, NextFunction, Request, Response } from 'express';
import { isAuthenticated } from '../middleware/auth';
import {
  DepartmentChain,
  UserDocument,
  UserLoginStatus,
  UserRole,
  UserSessionInfo,
} from '../model/user';
import {
  AsyncError,
  createSessionUser,
  fetchUserByLoginId,
  fetchUserDepartmentById,
  updateUserById,
} from '../util/asyncUtils';

/**
 * Check for auto-approval, this is ONLY required on first-registration.
 * Allowed Departments<ID, Name>:
 *   [Mobile Communications Business, C10D0165]
 *   [Big Data Center, 20018874C10A8900]
 *   [System R&D Group, 21020318C10A8906] // test purpose
 *
 */
const autoApproval = async (
  userDocument: UserDocument | undefined,
  user: UserSessionInfo,
): Promise<UserSessionInfo> => {
  let depId: any = userDocument?.departmentId;

  if (!depId) {
    console.error('depId is missing...!');
    return user;
  }

  depId = Array.isArray(depId) ? depId[0] : depId;

  try {
    const allowedDepartIds = ['20018874C10A8900', 'C10D0165'];
    const depChain: DepartmentChain = await fetchUserDepartmentById(depId);
    console.debug(`depChain: ${JSON.stringify(depChain)}`);
    // [Warn] API returns empty body for unknown depId
    const matchedDept =
      depChain.length > 0 &&
      depChain.find(dep => allowedDepartIds.includes(dep.departmentnumber));
    // reset approval status to false.
    let isApproved: boolean = false;
    if (matchedDept) {
      if (user.info.role === UserRole.Customer) {
        // auto-approval for Customer Role.
        isApproved = true;
      }
    }
    // update login status based on approval condition.
    if (user.status.loginStatus === UserLoginStatus.Approved && !isApproved) {
      console.warn('skip user revocation for the time being...');
      /*
      console.warn(
        `revocation of user(${user.info.loginid}), old ugroup(${user.info.ugroup}), new ugroup(${user.adfs?.groupNameEn})`,
      );
      // do not update ugroup here, it should be updated after registration again.
      user.status.loginStatus = UserLoginStatus.Revoked;
      */
    } else {
      user.status.loginStatus = isApproved
        ? UserLoginStatus.Approved
        : UserLoginStatus.Pending;
    }
    const updatedUser = await updateUserById(user);
    updatedUser.adfs = userDocument;
    console.info(
      `updated user(${updatedUser.info.loginid}, status: ${updatedUser.status.loginStatus})`,
    );
    return updatedUser;
  } catch (error) {
    // some errors ?
    console.error(`error during autoApproval: ${JSON.stringify(error)}`);
    return user;
  }
};

export const createUserRouter = (server: Application) => {
  const local = process.env.APP_ENV === 'localhost';
  const pathPrefix = '/user';
  server.get(
    `${pathPrefix}/info`,
    isAuthenticated,
    async (req: Request, res: Response, next: NextFunction) => {
      if (local && req.app.locals.name !== 'auth-test') {
        return res.status(200).json(req.app.locals.fakeUser);
      } else {
        /**
         * Return user info. from session for new comer,
         * otherwise fetch info. from the server.
         */
        const user = req.user as UserSessionInfo;
        if (user.status.loginStatus === UserLoginStatus.NewComer) {
          return res.status(200).json(user);
        } else {
          try {
            const newUser = await fetchUserByLoginId(user.info.loginid as string);
            console.info(`fetched user: ${JSON.stringify(newUser)}`);
            newUser.adfs = user.adfs; // ADSF is a session data.
            //@ts-ignore
            req._passport.session.user = newUser;
            req.session.save(err => {
              console.info(
                `current session ${req.sessionID} saved: ${
                  err ? err.message : true
                }, path: ${req.path}, depId: ${newUser.adfs?.departmentId}`,
              );
              res.status(200).json(newUser);
            });
          } catch (error) {
            const err: AsyncError = error;
            console.error(`fail to fetch user, reason: ${JSON.stringify(err.config)}`);
            const status: number = err.response
              ? err.response.status
              : err.request
              ? 502
              : 500;
            return res.status(status).json({
              reason: error.message,
            });
          }
        }
      }
    },
  );

  server.post(
    `${pathPrefix}/register`,
    isAuthenticated,
    express.json(),
    async (req: Request, res: Response, next: NextFunction) => {
      const user = req.user as UserSessionInfo;
      let tempUser: UserSessionInfo = user; // current session user by default.
      if (user.status.loginStatus === UserLoginStatus.Approved) {
        console.warn(`user(${user.info.loginid}) is already approved.`);
        return res.status(200).json(user);
      } else if (user.status.loginStatus === UserLoginStatus.NewComer) {
        // create user account for the NewComer.
        try {
          console.debug(
            `req.body: ${JSON.stringify(req.body)}, selected role: ${req.body.role}`,
          );
          tempUser = await createSessionUser({
            email: user.info.email,
            loginid: user.info.loginid,
            ugroup: user.info.ugroup,
            username: user.info.username,
            role: req.body.role, // update with user selected role.
          });
        } catch (error) {
          const err: AsyncError = error;
          console.error(`fail to create user, reason: ${JSON.stringify(err.config)}`);
          const status: number = err.response
            ? err.response.status
            : err.request
            ? 502
            : 500;
          res.status(status).json({
            reason: error.message,
          });
        }
      }
      // Update user status with given session info.
      const updatedUser = await autoApproval(user.adfs, tempUser);
      console.info(
        `current user: {${tempUser.info.loginid}, ${tempUser.status.loginStatus}},
          updated user: {${updatedUser.info.loginid}, ${updatedUser.status.loginStatus}}`,
      );
      //@ts-ignore
      req._passport.session.user = updatedUser;
      req.session.save(err => {
        console.info(
          `current session ${req.sessionID} saved: ${err ? err.message : true}, path: ${
            req.path
          }, depId: ${updatedUser.adfs?.departmentId}`,
        );
        res.status(200).json(updatedUser);
      });
    },
  );
};

/**
 * Check user login status for the following cases:
 *  - [Revocation] Change of Department in which user belongs to.
 *  - [Inactive Account] Elapased time since the last login. (MAX. 6 months)
 *
 * @param user - login user
 * @returns updated user object.
 */
export const validateUser = async (user: UserSessionInfo) => {
  // check for department changes.
  const tempUser = await autoApproval(user.adfs, user);
  const status = tempUser.status.loginStatus;
  console.info(`user: ${tempUser.info.loginid}, status: ${status}`);
  if (status === UserLoginStatus.Approved) {
    // user's approval status is still valid, then check for inactive user.
    const maxDays = 180;
    const fromDate = new Date(user.status.lastLoginTime as string);
    const diffInDays = (Date.now() - fromDate.getTime()) / (1000 * 3600 * 24);
    console.info(`lastLoginTime: ${fromDate}, days: ${diffInDays}`);
    if (diffInDays > maxDays) {
      user.status.loginStatus = UserLoginStatus.Inactive;
      console.info(
        `inactive user: ${user.info.loginid}, days since the last login: ${diffInDays}`,
      );
    }
    // update user's last login time.
    try {
      const updatedUser = await updateUserById(user);
      updatedUser.adfs = user.adfs;
      return updatedUser;
    } catch (error) {
      const err: AsyncError = error;
      console.error(
        `fail to update inactive user, reason: ${JSON.stringify(err.config)}`,
      );
    }
  }
  return tempUser;
};
